local gotDailies = CreateFrame("Frame", "gotDailies")
local maxQuestEntries = 2*MAX_QUESTS

-- Generic OnEvent handler
gotDailies:SetScript("OnEvent", function(self, event, ...)
	if type(self[event]) == "function" then
		return self[event](self, ...)
	end
end)

--		Grab a copy of LibgQuest
assert(LibStub, "gotDailies could not find an instance of LibStub.")
LibStub:GetLibrary("LibgQuest-1.0"):Embed(gotDailies)


-- Debugging
local DebugTable = {}
local DebugEnabled = true
function gotDailies:AddDebugLine(formatString, ...)
	if not DebugEnabled then
		return
	end
	
	DebugTable[#DebugTable + 1] = string.format(formatString, ...)
	if #DebugTable > 100 then
		table.remove(DebugTable, 1)
	end
end

function gotDailies:ToggleDebug()
	DebugEnabled = not DebugEnabled
	print("Debugging: "..DebugEnabled)
end

gotDailies.debug = DebugTable

--		Settings
BINDING_HEADER_gotDailies = "gotDailies?"
_G["BINDING_NAME_CLICK gotDailiesButton1:LeftButton"] = "gotDailies Button 1"
_G["BINDING_NAME_CLICK gotDailiesButton2:LeftButton"] = "gotDailies Button 2"

gotDailiesSettings = {}		-- Saved settings table.
gotDailies.defaults = {
	hideempty = true,
	rangecheck = true,
	priofirst = true,
	tooltip = true,
}

--		ButtonFacade support
local LBF = LibStub("LibButtonFacade", true)
if LBF then
	-- Set up LBF magic.
	gotDailiesSettings.LBF = {}
	gotDailies.LBFgroup = LBF:Group("gotDailies")
	local function SkinCallback(arg, SkinID, Gloss, Backdrop, Group, Button, Colors)
		gotDailiesSettings.LBF["SkinID"] = SkinID
		gotDailiesSettings.LBF["Gloss"] = Gloss
		gotDailiesSettings.LBF["Backdrop"] = Backdrop
	end
	LBF:RegisterSkinCallback("gotDailies", SkinCallback, "Hi!")
end

--		Scanning tooltip
local ScanTooltip = GotaiScanTooltip or CreateFrame("GameTooltip", "GotaiScanTooltip", UIParent, "GameTooltipTemplate")
ScanTooltip:SetOwner(UIParent, "ANCHOR_NONE")

--		Quest list management
-- Table to store questlists added by gotDailies:AddQuestlist.
-- Will be used as defaults if no data for a quest exists in the saved var.
local list = {}
function gotDailies:AddQuestlist(questlist)
	assert(type(questlist) == "table", "Bad argument #2 to AddQuestlist: table expected, got "..type(questlist))
	
	-- Get questnames for quests in the quest log. Store in saved vars?
	local line1, text = GotaiScanTooltipTextLeft1
	for k,v in pairs(questlist) do
		if not v.questname then
			ScanTooltip:SetHyperlink("|Hquest:"..k.."|h|h")
			text = line1:GetText()
			if text then
				v.questname = text
			end
		end
		list[k] = questlist[k]
	end
end

local questsperzone = {}
function gotDailies:GetQuestsPerZone()
	-- Clear the table
	for k,v in pairs(questsperzone) do
		for i=1,#v do
			v[i] = nil
		end
	end
	local zone
	for questid,_ in pairs(list) do
		if not rawget(self.QuestList, questid) then
			zone = self.QuestList[questid].zoneMajor
			if type(questsperzone[zone]) == "table" then
				questsperzone[zone][#questsperzone[zone] + 1] = questid
			else
				questsperzone[zone] = {questid}
			end
		end
	end
	for questid, details in pairs(self.QuestList) do
		zone = details.zoneMajor
		if type(questsperzone[zone]) == "table" then
			questsperzone[zone][#questsperzone[zone] + 1] = questid
		else
			questsperzone[zone] = {questid}
		end
	end
	return questsperzone
end

-- Cache table to avoid having to loop through the quest log every time
-- to fetch the name belonging to an ID.
local NameToIDTable = {}
local function GetQuestID(questname)
	if NameToIDTable[questname] then
		gotDailies:AddDebugLine("GetQuestID; found questname in lookup table; questname = %q, returned: %q", questname, NameToIDTable[questname])
		return NameToIDTable[questname]
	else
		local questTitle, level, questTag, suggestedGroup, isHeader, isCollapsed, isComplete, isDaily, questID
		for i=1, maxQuestEntries do
			questTitle, level, questTag, suggestedGroup, isHeader, isCollapsed, isComplete, isDaily, questID = GetQuestLogTitle(i)
			if not questTitle then
				-- First nil entry means we're out of quests.
				gotDailies:AddDebugLine("GetQuestID; did not find questname %q in questlog.", questname)
				return
			end
			if questTitle == questname then
				NameToIDTable[questname] = questID
				gotDailies:AddDebugLine("GetQuestID; questname %q found in questlog. Returning: %d", questname, questID)
				return questID
			end
		end
	end
end

local function AddNameToCache(questname, questID)
	NameToIDTable[questname] = questID
end

-- Function to schedule a function for leaving combat.
local schedule = {}
function gotDailies:ScheduleForLeavingCombat(object, func, ...)
	-- If object is a table, func will be the method that needs to be executed.
	-- Otherwise func will be the first arg.
	if type(object) == "table" then
		if select("#", ...) > 1 then
			schedule[#schedule + 1] = {object = object, func = func, args = {...}}
		else
			schedule[#schedule + 1] = {object = object, func = func, args = ...}
		end
	else
		if ... then
			schedule[#schedule + 1] = {func = object, args = {func, ...}}
		else
			schedule[#schedule + 1] = {func = object, args = func}
		end
	end
	-- Register PLAYER_REGEN_ENABLED if it isn't.
	if not self:IsEventRegistered("PLAYER_REGEN_ENABLED") then
		self:RegisterEvent("PLAYER_REGEN_ENABLED")
	end
end

local zoneMajor, zoneMinor, zoneMinimap	-- These are set whenever the zone changes.
local ActiveQuests = {} 		-- Table to store our currently active quests in.

-- Saved var list with the local list questlist as index. Saves loading time due to less duplicate code... I think.
-- Or maybe it just kills performance and gives me headaches. *shrug* If so, gimme a shout. ;)
local QuestList

-- Table sorting to manage priority.
local sort = table.sort
local SortFunction = function(a,b)
	return (QuestList[a].prio or 0) > (QuestList[b].prio or 0)
end

function gotDailies:UpdateButtons()
	if not zoneMajor or not zoneMinor then
		zoneMajor, zoneMinor = GetRealZoneText(), GetSubZoneText()
		zoneMinimap = GetMinimapZoneText()
	end

	-- Check if there's an active quest for this zone.
	if not ActiveQuests[zoneMajor] or #ActiveQuests[zoneMajor] == 0 then
		return
	end
	
	-- Sort ActiveQuest table for current zone based on quest priority.
	sort(ActiveQuests[zoneMajor], SortFunction)
	local questtable, questid
	for i=1, #ActiveQuests[zoneMajor] do
		questid = ActiveQuests[zoneMajor][i]
		questtable = QuestList[questid]

		-- Check if we're in the right subzone for this quest.
		local setbutton
		if type(questtable.zoneMinor) == "table" then
			for i=1, #questtable.zoneMinor do
				if questtable.zoneMinor[i] == zoneMinor or questtable.zoneMinor[i] == "all" then
					setbutton = true
				end
			end
		elseif questtable.zoneMinor == zoneMinor or questtable.zoneMinor == zoneMinimap or questtable.zoneMinor == "all" then
			setbutton = true
		end
		
		if setbutton then
			gotDailies:SetButton(questid, questtable)
		end
	end
end

-- Checks questlink and adds it to the active quest table if daily data is found.
function gotDailies:CheckQuest(questName, questID)
	local t = QuestList[questID]
	if t then
		-- Add questname, questID pair to cache table.
		AddNameToCache(questName, questID)
		
		if not t.questname then
			-- Add the questname to the list to allow easier selection in the config
			-- without the need for localisation.
			t.questname = questName
		end
		
		local zone = t.zoneMajor
		if not ActiveQuests[zone] then
			ActiveQuests[zone] = {}
		end
		
		for i=1, #ActiveQuests[zone] do
			if ActiveQuests[zone][i] == questID then
				gotDailies:AddDebugLine("%q (id: %d) already found in ActiveQuests table, returning", questName, questID)
				return
			end
		end
		ActiveQuests[zone][#ActiveQuests[zone] + 1] = questID
		gotDailies:AddDebugLine("Added %q to ActiveQuests table; questID %d", questName, questID)
	end
end

-- Remove quest from the active quest table.
local function RemoveQuestByName(questname)
	local questID = GetQuestID(questname)

	gotDailies:AddDebugLine("RemoveQuestByName start table iteration; questname = %q, questID = %d", questname, questID)
	if QuestList[questID] then
		local t = ActiveQuests[QuestList[questID].zoneMajor]
		for i=1, #t do
			if t[i] == questID then
				table.remove(t, i)
			end
		end
		
		-- Check if any of the buttons is currently set up for this quest, and clear it if that's the case.
		for i=1, #gotDailies.buttons do
			if gotDailies.buttons[i].data.questid == questID then
				gotDailies.buttons[i]:Clear()
			end
		end
		gotDailies:UpdateButtons()
	end
	
	-- Remove the quest from the cache table.
	NameToIDTable[questname] = nil
end


-- Event functions.	

-- Initialisation
gotDailies:RegisterEvent("ADDON_LOADED")
gotDailies.ADDON_LOADED = function (self, addon)
	if addon == "gotDailies" then
		if type(gotDailiesSettings) ~= "table" then
			gotDailiesSettings = {}
		end
		if not gotDailiesSettings.list then
			gotDailiesSettings.list = {}
		end
		
		self.settings = setmetatable(gotDailiesSettings, {__index = self.defaults})
		QuestList = setmetatable(gotDailiesSettings.list, {__index = list})
		self.QuestList = QuestList
		
		if LBF then
			self.LBFgroup:Skin(gotDailiesSettings.LBF.SkinID, gotDailiesSettings.LBF.Gloss, gotDailiesSettings.LBF.Backdrop)
		end
		for i=1,2 do
			self:CreateButton()
		end
	end
end

-- Loop through schedule on leaving combat.
-- The event is registered only when needed.
gotDailies.PLAYER_REGEN_ENABLED = function(self)
	local t, obj, func, args
	for i=1, #schedule do
		t = schedule[i]
		obj, func, args = t.object, t.func, t.args
		if obj then
			if type(args) == "table" then
				obj[func](obj, unpack(args))
				t.args = nil
			else
				obj[func](obj, args)
			end
		else
			if type(args) == "table" then
				func(unpack(args))
				t.args = nil
			else
				func(args)
			end
		end
		t = nil
		schedule[i] = nil
	end
	
	self:UnregisterEvent("PLAYER_REGEN_ENABLED")
end

-- Populate the ActiveQuests table on login/reload.
gotDailies:RegisterEvent("QUEST_LOG_UPDATE")
gotDailies.QUEST_LOG_UPDATE = function(self)
	self:UnregisterEvent("QUEST_LOG_UPDATE")
	local link
	local _,done
	for i=1, maxQuestEntries do
		questTitle, level, questTag, suggestedGroup, isHeader, isCollapsed, isComplete, isDaily, questID = GetQuestLogTitle(i)
		if not questTitle then
			return
		end
		if not isComplete and not isHeader then
			gotDailies:AddDebugLine("QUEST_LOG_UPDATE; passing questTitle %q, questID %d on to CheckQuest.", questTitle, questID)
			self:CheckQuest(questTitle, questID)
		end
	end
	self:UpdateButtons()
end

function gotDailies:UpdateZones()
	zoneMajor, zoneMinor = GetRealZoneText(), GetSubZoneText()
	zoneMinimap = GetMinimapZoneText()
	self.zoneMajor, self.zoneMinor, self.zoneMinimap = zoneMajor, zoneMinor, zoneMinimap
	self:UpdateButtons()
end

-- For some reason if we listen to the events the new zone info is not
-- always queryable, so instead we check after the zonetext frame has
-- displayed the new zone text.
ZoneTextFrame:HookScript("OnEvent", function(self, event)
	return gotDailies:UpdateZones()
end)

-- Some times ZONE_CHANGED(_NEW_AREA) doesn't fire on login, 
-- use this to catch the zone names should that occur.
gotDailies:RegisterEvent("WORLD_MAP_UPDATE")
gotDailies.WORLD_MAP_UPDATE = function(self)
	if GetRealZoneText() and GetRealZoneText() ~= "" then
		self:UnregisterEvent("WORLD_MAP_UPDATE")
		return self:UpdateZones()
	end
end

-- Zone changed events don't fire on reload. Catch them in PEW.
gotDailies:RegisterEvent("PLAYER_ENTERING_WORLD")
gotDailies.PLAYER_ENTERING_WORLD = function(self)
	local zoneMajor, zoneMinor = GetRealZoneText(), GetSubZoneText()
	if zoneMajor and zoneMajor ~= "" and zoneMinor then
		self:UnregisterEvent("PLAYER_ENTERING_WORLD")
		return self:UpdateZones()
	end
end

gotDailies:RegisterEvent("QUEST_ACCEPTED")
gotDailies.QUEST_ACCEPTED = function(self, index)
	local questName, level, questTag, suggestedGroup, isHeader, isCollapsed, isComplete, isDaily, questID = GetQuestLogTitle(index)
	gotDailies:AddDebugLine("QUEST_ACCEPTED; index %d; passing questName %q and id on to CheckQuest", questID, questName)
	gotDailies:CheckQuest(questName, questID)
	return gotDailies:UpdateButtons()
end

local function RemoveQuestWrapper(event, index)
	gotDailies:AddDebugLine("RemoveQuestWrapper called; event = %q, index = %q", (event or "nil"), (index or "nil"))	
	-- Index will be the questname when the quest is abandoned.
	return RemoveQuestByName(tonumber(index) and GetQuestLogTitle(index) or index)
end

gotDailies:RegisterCallback("QUEST_ABANDONED", RemoveQuestWrapper)
gotDailies:RegisterCallback("QUEST_COMPLETED", RemoveQuestWrapper)

-- Slash command to open the menu.
SlashCmdList["gotDailies"] = function(input)
	if input:find("move") then
		if InCombatLockdown() then return print("Can not move buttons in combat.") end
		for i,button in ipairs(gotDailies.buttons) do
			if button:IsShown() then
				button:Hide()
				button.name:Hide()
			else
				button:Show()
				button.name:Show()
			end
		end
	else
		local loaded, reason = IsAddOnLoaded("gotDailies_Config")
		if not loaded then
			loaded, reason = LoadAddOn("gotDailies_Config")
		end
		if not loaded then
			return ChatFrame1:AddMessage("Could not load the Config module, reason: ".._G["ADDON_"..reason])
		end
		
		InterfaceOptionsFrame_OpenToCategory("gotDailies")
	end
end

SLASH_gotDailies1 = "/gotdailies"
SLASH_gotDailies2 = "/gd"